package videosearch;

import java.awt.Color;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;

/**
 *
 * @author John The VideoProcessor is designed to hold the processed video data
 * after it has been run through the descriptor process. This class also
 * performs the matches
 */
public class VideoProcessor {
    // Class variables

    static final int Width = 352; // Width of input video
    static final int Height = 288; // Height of input video
    String fileName;
    ArrayList<Frame> frames;

    // Constructor for creation, takes the file it needs to read in and
    // automatically performs the characterization on it
    public VideoProcessor(String file) {
        fileName = file;
        frames = new ArrayList();
        characterize();
    }

    // Second constructor if reading in a pre-characterized file. filename for
    // reference as well as the data which is the characterization
    public VideoProcessor(String file, String data) {
        fileName = file;
        frames = new ArrayList();
        parse(data);
    }

    // Function to characterize a .rgb file
    public void characterize() {
        File file = null;
        InputStream is = null;

        try {
            file = new File(fileName); // read in the file
            is = new FileInputStream(file);

            int frameLength = Width * Height * 3;
            byte[] bytes = new byte[frameLength]; // bytes to hold a byte

            // read in the data as binary bytes
            while (is.read(bytes) >= 0) {
                int ind = 0;
                float h = 0, s = 0, v = 0; // local variables
                for (int y = 0; y < Height; y++) { // iterate over height
                    for (int x = 0; x < Width; x++) { // iterate over width
                        int r = clamp(bytes[ind], 0, 255);
                        int g = clamp(bytes[ind + Height * Width], 0, 255);
                        int b = clamp(bytes[ind + Height * Width * 2], 0, 255);

                        // Create an HSV representation of the frame, then sum
                        // those values and create an average h, s, and v for
                        // the overall frame. These values will characterize
                        // the frame, and be used to match
                        float[] hsb = Color.RGBtoHSB(r, g, b, new float[3]);
                        h += hsb[0];
                        s += hsb[1];
                        v += hsb[2];

                        ind++;
                    }
                }
                float[] hsv = {h / ind, s / ind, v / ind};
                frames.add(new Frame(hsv)); // create a new Frame object
            } // While Frames left to read

        } catch (FileNotFoundException e) {
            System.out.println("Couldn't find the file: " + fileName);
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println("IO Exception occured");
            System.out.println(e.getMessage());
        } finally {
            closeInputStream(is);
        }
    }

    // Closes the input stream
    private void closeInputStream(InputStream stream) {
        try {
            if (stream != null) {
                stream.close();
            }
        } catch (IOException e) {
            System.out.println("IO Exception occured");
            System.out.println(e.getMessage());
        }
    }

    // Performs the find, takes a videoprocessor to get the query hsv values
    public ArrayList<Double> find(VideoProcessor query) {
        ArrayList<Double> results = new ArrayList();
        // Get the characterization values from the query video
        ArrayList<Frame> queryFrames = query.frames;

        // Move through all the frames, search for the query frames as one
        // contiguous block
        for (int i = 0; i < frames.size() - queryFrames.size() + 1; i++) {
            double match = 0;
            for (int j = 0; j < queryFrames.size(); j++) {
                // Attempt to match the query from this frame
                match += match(frames.get(i + j), queryFrames.get(j));
            }
            match /= queryFrames.size();
            // Add a scaled version (100) to the list of results, an entry for
            // every frame in the video.
            results.add(match);
        }

        return results;
    }

    // Takes two frames, computes the "match" based on 3 dimensional closeness
    private double match(Frame first, Frame second) {
        double h = (first.hsv[0] * 360) - (second.hsv[0] * 360);
        double s = (first.hsv[1] * 100) - (second.hsv[1] * 100);
        double v = (first.hsv[2] * 100) - (second.hsv[2] * 100);
        double value = Math.sqrt(Math.pow(h, 2)
                + Math.pow(s, 2)
                + Math.pow(v, 2));
        return value;
        //return ( value / 386.78159211627432360648678656591 ) * 100;
    }

    // Writes out the characterization data in this class to a file, used to
    // create the initial database
    public void write(PrintWriter out) {
        out.print(fileName + ",");
        for (int i = 0; i < frames.size(); i++) {
            out.print(frames.get(i).h() + ",");
            out.print(frames.get(i).s() + ",");
            out.print(frames.get(i).v());
            if (i != frames.size() - 1) {
                out.print(",");
            }
        }
        out.println();
        out.flush();
    }

    // Parse in the data given from a flat database file
    private void parse(String _data) {
        String[] data = _data.split(","); // split on csv
        for (int i = 0; i < data.length; i += 3) { // create new frames
            float[] newFrame = {Float.parseFloat(data[i]),
                Float.parseFloat(data[i + 1]),
                Float.parseFloat(data[i + 2])};
            frames.add(new Frame(newFrame));
        }
    }

    // Simple clamp helper function
    private int clamp(int val, int low, int high) {
        if (val > high) {
            return high;
        }
        if (val < low) {
            return low;
        }
        return val;
    }
}

// Frame helper class, acts as a container for the characterization of frames,
// could be later extended to hold more specific information
class Frame {
    // Variables

    public float[] hsv;

    // Constructor
    public Frame(float[] hsv) {
        this.hsv = hsv;
    }

    // getters and setters
    public float h() {
        return hsv[0];
    }

    public float s() {
        return hsv[1];
    }

    public float v() {
        return hsv[2];
    }
}
